#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include<mpi.h>
#include<ctype.h>

#define W_SIZE 1000
#define N_MAX 3000
#define N_PRIMES 367

/*
 * Vector with all the prime numbers from 2 to 2500
 */
int PrimeNumbers[N_PRIMES];


int isEndOfPhrase(char c);
void findCutoffs(int *cutoffs, char *text, long int fileSize, int nThreads);
void ClearString(char *String);
void CrivosAlgorithm();
int isPrime(char *String);
int isPalindrome(char *String);
int inTheWord(char c);
void CountOnFile(char *StringTemp, long int sizeFile, int Numbers[]);
void copyBuffer(char *text, int begin, int end, char *new);



int main(int argc, char* argv[]){
    int nPalindW = 0, nPrimes = 0;
    long int fileSize;
    FILE *f = fopen("wikipedia.txt", "r");
    int *cutoffs;
    int destiny = 0, source = 0;
    int begin, end;
    int n[2];
    int threadNumber;
    int nThreads;
    char block[8000000];
    char *text;
    int outBegin, outEnd;
    int i;
    MPI_Status Stat;

    MPI_Init(&argc, &argv);
    MPI_Comm_rank(MPI_COMM_WORLD, &threadNumber); /*Obtem o numero da Thread*/
    MPI_Comm_size(MPI_COMM_WORLD, &nThreads);

    cutoffs = (int*) malloc((nThreads + 1) * sizeof(int));

    if(threadNumber == 0){ /*Trecho Sequencial do Codigo*/
	CrivosAlgorithm();
	if(f == NULL) {
	    printf("Nao foi possivel abrir o arquivo\n");
	    exit(1);
	}
	/*
	 * calcula o tamanho do arquivo de texto
	 */
	fseek(f, 0, SEEK_END);
	fileSize = ftell(f);
	/*
	 * retorna o cursor para o inicio do arquivo
	 */
	rewind(f);
	/* aloca um vetor de char para armazenar todo o arquivo
	 * em memoria principal
	 */
        text = (char*) malloc((fileSize + 1)*sizeof(char));
	if(text == NULL) {
	    printf("Erro ao tentar alocar memoria para o texto\n");
	    exit(1);
	}
	/*
	 * salva todo o arquivo no vetor 'text'
	 */
	fread(text, sizeof(char), fileSize + 1, f);
	/*
	 * encontra as posicoes do vetor 'text' dentro das quais cada
	 * processo vai computar
	 */
	findCutoffs(cutoffs , text, fileSize, nThreads);
	for(i = 1; i < nThreads; i++) {
	    outBegin = cutoffs[i];
	    outEnd = cutoffs[i + 1];
	    /* copia o trecho a ser  enviado para o vetor 'block' */
	    copyBuffer(text, outBegin, outEnd, block);

	    /*
	     * envia para o i-esimo processo os seguintes dados:
	     * - PrimeNumbers[] - vetor com a lista de nros primos
	     * - outBegin - inicio do bloco de texto a ser processado
	     * - outEnd - fim do bloco de texto a ser processado
	     * - block[] - vetor com o bloco de texto a ser processado
	     */
	    MPI_Send(&PrimeNumbers, N_PRIMES, MPI_INT, i, 1, MPI_COMM_WORLD);
	    MPI_Send(&outBegin, 1, MPI_INT, i, 1, MPI_COMM_WORLD);
	    MPI_Send(&outEnd, 1, MPI_INT, i, 1, MPI_COMM_WORLD);
	    MPI_Send(&block, outEnd - outBegin, MPI_CHAR, i, 1, MPI_COMM_WORLD);
	}
    } else {
	/*
	 * recebe os seguintes dados:
	 * - PrimeNumbers[] - vetor com a lista de nros primos
	 * - begin - inicio do bloco de texto a ser processado
	 * - end - fim do bloco de texto a ser processado
	 * - block[] - vetor com o bloco de texto a ser processado
	 */
	MPI_Recv(&PrimeNumbers, N_PRIMES, MPI_INT, source, 1, MPI_COMM_WORLD, &Stat);
	MPI_Recv(&begin, 1, MPI_INT, source, 1, MPI_COMM_WORLD, &Stat);
	MPI_Recv(&end, 1, MPI_INT, source, 1, MPI_COMM_WORLD, &Stat);
	MPI_Recv(&block, end - begin, MPI_CHAR, source, 1, MPI_COMM_WORLD, &Stat);
    }
    /*
     * delimita o inicio e fim do bloco de texto a ser processado
     * pelo processo 0
     */
    if(threadNumber == 0) {
	begin = cutoffs[0]; /* obtem o inicio do bloco a ser analisado */
	end = cutoffs[1]; /* obtem o fim do bloco a ser analisado */
	/*
	 * calcula o nro de palindromos no primeiro bloco do vetor 'text'
	 * o processo 0 nao possui seu bloco de texto no vetor 'block', por isso
	 * o calculo eh feito direto no vetor 'text'
	 */
	CountOnFile(text, end, n); /* o processo 0 nao copiou o texto para o vetor 'block' */
    } else {
	/*
	 * calcula o nro de palindromos no bloco de texto presente no vetor 'block'
	 */
	CountOnFile(block, end - begin, n);
    }
    /*
     * faz a reducao dos valores encontrados de nro de palindromos e nro  de palindromos cuja
     * soma ascii eh um primo, somando os valores encontrados em cada processo nas variaveis
     * 'nPalindW' e 'nPrimes', respectivamente
     */
    MPI_Reduce(&n[0], &nPalindW, 1, MPI_INT, MPI_SUM, destiny, MPI_COMM_WORLD);
    MPI_Reduce(&n[1], &nPrimes, 1, MPI_INT, MPI_SUM, destiny, MPI_COMM_WORLD);
    if(threadNumber == 0){
	printf("Total of Palindromes found:\nWords:\t%d\nPrimes:  %d\n\n", nPalindW, nPrimes);
    }
    MPI_Finalize();

    return 0;
}



/*
 * Verifica se um caracter eh um dos caracteres que
 * delimitam frases
 * @ ENTRADA
 *   letter - caracter que se quer verificar
 * @ SAIDA
 *   int - 1 se o caracter for um delimitador de frase,
 *         e 0 caso contrario
 */
int isEndOfPhrase(char c)
{
    //if the char is one of this symbols, its an end of phrase
    if(c == '.' || c == ';' || c == '(' || c == ')' || c == '"' ||
       c == ':' || c == '<' || c == '>' || c == '{' || c == '}' ||
       c == '[' || c == ']' || c == '|' || c == '&' || c == '!' ||
       c == '?' || c == '\n'|| c == '\t'|| c == '\r' )
       return 1;
    else return 0;
}

/*
 * Encontra as posicoes do vetor com o texto dentro das quais
 * cada thread vai atuar
 * @ ENTRADA
 *   *text - vetor de caracter com o texto analisado
 *   fileSize - tamanho do vetor de texto
 *   nThreads - numero de threads
 * @ SAIDA
 *   *cutoffs - vetor de inteiros com as posicoes que delimitam
 *              cada bloco
 */
void findCutoffs(int *cutoffs, char *text, long int fileSize, int nThreads)
{
  int blockSize;
  int i;

  /*
   * calcula o tamanho aproximado que os blocos de caracteres que cada tread
   * vai computar devem ter
   */
  blockSize = fileSize / nThreads;
  cutoffs[0] = 0; /* o inicio do 1o bloco eh sempre zero */
  /*
   * encontra, no texto, o proximo final de frase a partir
   * de cada ponto que deveria marcar o fim de bloco
   */
  for(i = 1; i < nThreads; i++) {
    /* recebe a posicao que deveria marcar o fim do i-esimo bloco */
    cutoffs[i] = i * blockSize;
    while( !isEndOfPhrase(text[ cutoffs[i] ]) )
      cutoffs[i]++;
    cutoffs[i]++;
  }
  /* o final do ultimo bloco sempre eh o fim do texto */
  cutoffs[i] = fileSize;
}



/*
 * Function: ClearString
 * Params: char *String
 * Return: void
 * Clean the String received
 * */
void ClearString(char *String){
    //Gets the size of the string and change every char for a '\0'
    int i, size = strlen(String);
    for(i=0; i< size; i++){
        String[i] = '\0';
    }
}

/*
 * Function: CrivosAlgorithm
 * Param: void
 * Return: void
 * Calculates all the prime numbers from 2 to 2500.
 * The numbers are available to be consulted in the global vector PrimeNumbers*/
void CrivosAlgorithm(){
    // We create the local variables and set the initial values
    int auxVector[N_MAX];
    int check = sqrt(N_MAX);
    int i;
    int aux=0;
    int inc =0;

    //Fill the auxVector with numbers from 2 to N_MAX+2
    for(i=0; i<N_MAX; i++){
        auxVector[i] = i+2;
    }

    //Eliminates the non-prime numbers (multiples of i)
    for(i=0; i<check; i++){
        if(auxVector[i] != 0){
            inc = auxVector[i];
            aux = i+inc;

            while(aux < N_MAX){
                if(auxVector[aux] != 0){
                    auxVector[aux] = 0;
                }
                aux += inc;
            }
        }
    }

    //Fill the Global Vector
    aux=0;
    for(i=0; i<N_PRIMES; i++){
        if(auxVector[i] >0){
            PrimeNumbers[aux] = auxVector[i];
            aux++;
        }
    }
}

/*
 * Function: isPrime
 * Param: char *String
 * return: int : 1 - TRUE; 0 - FALSE
 * The function will sum the ascii values of the String and check if this number is in the PrimeNumbers vector.
 * If it is, the function return TRUE (1) else return FALSE (0)*/
int isPrime(char *String){
    int sum = 0;
    int i;

    //Sum the ASCII values from string
    for(i=0; i<strlen(String); i++){
        sum += String[i];

    }
    //Verify if the sum is in the Global Vector
    for(i=0; i<N_PRIMES; i++){
        if(PrimeNumbers[i] > sum){
            break;
        }
        else if(PrimeNumbers[i] == sum){
            return 1;
        }
    }
    return 0;
}

/*
 * Function: isPalindrome
 * Param: char *String
 * return: int: 1 - TRUE; 0 - FALSE
 * The function will check is the String is a Palindrome
 * */
int isPalindrome(char *String){
    // We create the local variables and set the initial values
    char inverse_String[W_SIZE];
    int count, size = strlen(String);
    int i;

    long v = String[0];

    // If the String isn't just a letter, i.e: 'a', verify if its a palindrome
    if(strlen(String) > 1 && v != '\0'){
        //transform all the letters to lower chars
        for(i=0; i<size; i++){
            String[i] = tolower(String[i]);
        }

        count = size-1;
        ClearString(inverse_String);
        //copy the string to a aux string in the inverse
        for(i=0; i<size; i++){
            inverse_String[i] = String[count];
            count--;
        }
        //compare both strings
        if(strcmp(inverse_String, String) == 0){
            return 1;
        }
        else return 0;
    }
    else return 0;
}

/*
 * Function: inTheWord
 * Param: char c
 * return int: 1 _ TRUE; 0 - FALSE
 * Verify if the char is in the word
*/
int inTheWord(char c){
    //if the char is one of this symbols, its not the final of the word
    if(((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || ((c >= '0') && (c <= '9')))
        return 1;

    else return 0;
}

/*
 * Function: CountOnFile
 * Param: FILE *f
 * Return: Int vector with the number of palindromes and primes
 *         [0] Number of Palindrome words
 *         [1] Number of Prime Numbers
 * The function count from the file f the number of Palindrome Words
 * and the number of words with ascii value equals a prime number*/
void CountOnFile(char *StringTemp, long int sizeFile, int Numbers[]){
    // Create and Set the variables and its initials values
    char Word[W_SIZE];
    char c;

    int i;
    long int counter=0;

    ClearString(Word);

    Numbers[0] = 0;
    Numbers[1] = 0;

    // For each char of the file, analyses if indicates an end of word
    for(i=0; i<sizeFile; i++){
        c = StringTemp[i];
        //If it is a letter or number, continue the analysis
        if( inTheWord(c) ){
            Word[counter] = c;
            counter++;
        }
        else{
            //Verify if the word is a palindrome
            if(isPalindrome(Word) && strlen(Word) > 1){
              Numbers[0]++;
              //verify if the sum of the ascii values is a prime number
              if(isPrime(Word) && strlen(Word)) {
                Numbers[1]++;
              }
            }

            ClearString(Word);
            counter=0;
        }
    }
    ClearString(StringTemp);
}


/*
 * copia um bloco de um vetor em um novo vetor
 * @ENTRADA
 *   *text - vetor que cujo bloco se quer copiar
 *   begin - posicao na qual o bloco a ser copiado inicia
 *   end   - posicao na qual o bloco a ser copiada encerra
 * @SAIDA
 *   *new  - vetor que vai receber o bloco copiado
 */
void copyBuffer(char *text, int begin, int end, char *new) {
    int i;

    for(i = begin;  i <= end; i++)
	new[i  - begin] = text[i];
}

